﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using ConceptsParser.SyntaxTree;
using Compiler.CompilerAPI;
using Compiler.CompilerAPI.Contexts;
using Compiler.TableOfSymbols.Types;
using Compiler.Unification.Constraints;
using Compiler.Unification;

namespace Compiler.SyntaxTreeAnalysis
{
    public class ConceptVisitor : ISyntaxTreeVisitor
    {
        private CompilationEnvironment environment;
        private ConceptContext currentConceptContext;

        /// <summary>
        /// Вспомогательный флаг для проверки необходимости
        /// дальнейшего анализа (если false, на каком-то этапе анализа концепта
        /// уже встретилась ошибка)
        /// </summary>
        private bool success;

        /*public CompilationEnvironment Environment
        { get { return environment; } }
        public ConceptContext CurrentConceptContext
        { get { return currentConceptContext; } }*/

        public ConceptVisitor(CompilationEnvironment environment, ConceptContext currentConceptContext)
        {
            if (environment == null)
                throw new ArgumentNullException("environment can not be null");
            if (currentConceptContext == null)
                throw new ArgumentNullException("currentConceptContext can not be null");
            this.environment = environment;
            this.currentConceptContext = currentConceptContext;
            this.success = true;
        }

        public void Visit(ConceptsSyntaxTreeNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(ProgramNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(ProgramSectionNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(DeclarationNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(DirectiveNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(DefinitionNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(QualifiedNamespaceNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(TypeQualifierNode node)
        {
            // TODO
            throw new NotImplementedException();
        }

        public void Visit(IDTypeQualifierNode node)
        {
            // TODO
            throw new NotImplementedException();
        }

        public void Visit(ConceptReferenceQualifierNode node)
        {
            // TODO
            throw new NotImplementedException();
        }

        public void Visit(TypeArgumentNode node)
        {
            // TODO
            throw new NotImplementedException();
        }

        private BaseType _ProcessTypeArgument(TypeArgumentNode node)
        {
            // TODO concept req
            // NETTypeName это имя класса или имя + число аргументов для обобщённого класса
            TypeSearchInfo searchResult = environment.WorkingContext.TryGetTypeInfo(
                node.NETTypeName, node.TypeQualifierString);
            // ошибка при поиске типа
            if (searchResult.TypeInfo == null)
            {
                string errorMessage;
                if (searchResult.UnknownType)
                    errorMessage = String.Format("Неизвестный тип {0}", node);
                else
                    errorMessage = String.Format("Невозможно однозначно определить тип {0}", node);
                environment.ErrorManager.AddSemanticError(
                    new SemanticError(errorMessage, node.Location));
                this.success = false;
                return null;
            }
            if (node is SimpleTypeArgumentNode)
                return _ProcessTypeArgument(node as SimpleTypeArgumentNode, searchResult.TypeInfo);
            else //(node is GenericTypeArgumentNode)
                return _ProcessTypeArgument(node as GenericTypeArgumentNode, searchResult.TypeInfo);
        }

        private BaseType _ProcessTypeArgument(SimpleTypeArgumentNode node, BaseType typeInfo)
        {
            //if (!(typeInfo is SimpleType))
            //{
            //    environment.ErrorManager.AddSemanticError(new SemanticError(
            //        String.Format("Не указаны типы-аргументы обобщённого типа {0}", node), 
            //        node.Location));
            //    this.success = false;
            //    return null;
            //}
            // typeInfo may be type variable or simple class
            bool isSimpleNetType = typeInfo.IsTypeValue && (typeInfo as TypeValue).IsNETClassTypeValue
                && (typeInfo as ClassTypeValueNET).IsSimpleType;
            if (isSimpleNetType && !environment.TypesBijectiveMap[typeInfo].IsClass)
            {
                environment.ErrorManager.AddSemanticError(new SemanticError(
                    String.Format("Тип {0} не является классом (а поддерживаются только типы-классы)", node),
                    node.Location));
                this.success = false;
                return null;
            }
            else
                return typeInfo;
        }

        private BaseType _ProcessTypeArgument(GenericTypeArgumentNode node, BaseType typeInfo)
        {
            //if (!(typeInfo is GenericClassTypeValue))
            //{
            //    environment.ErrorManager.AddSemanticError(new SemanticError(
            //        String.Format("Тип {0} не является обобщённым", node),
            //        node.Location));
            //    this.success = false;
            //    return null;
            //}
            Type NETTypeInfo = environment.TypesBijectiveMap[typeInfo];
            Type[] typeParams = NETTypeInfo.GetGenericArguments();
            // число аргументов сидело в имени
            //if (typeParams.Length != node.TypeArguments.Count)
            //{
            //    environment.ErrorManager.AddSemanticError(new SemanticError(
            //        String.Format("Неверное число типовых аргументов обобщённого типа {0}: ожидалось {1}",
            //        node, typeParams.Length), node.Location));
            //    this.success = false;
            //    return null;
            //}
            foreach (Type typeParam in typeParams)
            {
                Type[] constraints = typeParam.GetGenericParameterConstraints();
                if (constraints.Length > 0)
                {
                    environment.ErrorManager.AddSemanticError(new SemanticError(
                        String.Format("Обобщённый тип {0} содержит ограничения на типовые параметры (такие типы не поддерживаются)",
                        node), node.Location));
                    this.success = false;
                    return null;
                }
            }
            BaseType[] typeArgs = new BaseType[node.TypeArguments.Count];
            int i = 0;
            foreach (TypeArgumentNode typeArgNode in node.TypeArguments)
            {
                typeArgs[i] = _ProcessTypeArgument(typeArgNode);
                if (!this.success)
                    return null;
                ++i;
            }
            // в результате обработки типы-аргументы добавились в глобальную таблицу
            Type[] NETTypeArgs = new Type[typeArgs.Length];
            for (int j = 0; j < typeArgs.Length; ++j)
                NETTypeArgs[j] = environment.TypesBijectiveMap[typeArgs[j]];
            try
            {
                Type NETConstructedType = NETTypeInfo.MakeGenericType(NETTypeArgs);
                BaseType constructedType;
                if (environment.TypesBijectiveMap.Contains(NETConstructedType))
                    constructedType = environment.TypesBijectiveMap[NETConstructedType];
                else
                {
                    constructedType = environment.TypesBijectiveMap.MakeTypeInfo(NETConstructedType);
                    environment.TypesBijectiveMap.AddPair(constructedType, NETConstructedType);
                }
                return constructedType;
            }
            catch (ArgumentException exc)
            {
                environment.ErrorManager.AddSemanticError(new SemanticError(
                    String.Format("Неожиданно нельзя инстанцировать тип {0} указанными параметрами ({1})",
                    node, exc.Message), node.Location));
                if (!this.success)
                    return null;
            }
            // сюда выполнение не доходит
            return null;
        }

        public void Visit(SimpleTypeArgumentNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(GenericTypeArgumentNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(UsingSectionNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(UsingDirectiveNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(ConceptsSectionNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(ConceptDefinitionNode node)
        {
            // TODO
            Visit(node.Signature);
            if (!this.success)
                return;
            if (node.Refinement != null)
            {
                Visit(node.Refinement);
                if (!this.success)
                    return;
            }
            Visit(node.Body);
        }

        public void Visit(ConceptSignatureNode node)
        {
            // TODO
            // заголовок нового концепта
            if (node.IsNewConceptDef)
            {
                HashSet<string> typeParamNames = new HashSet<string>();
                foreach (string paramName in node.TypeParams)
                    if (typeParamNames.Contains(paramName))
                    {
                        environment.ErrorManager.AddSemanticError(new SemanticError(
                            String.Format("Типовый параметр концепта {0} с именем {1} уже определён",
                                node.Name, paramName), node.Location));
                        this.success = false;
                        return;
                    }
                    else
                    {
                        typeParamNames.Add(paramName);
                        var typeVarInfo = currentConceptContext.AddConceptTypeParameter(paramName);
                        // TODO
                        Type NETTypeVarInfo = new NETTypeVariable(typeVarInfo.Name);
                        environment.TypesBijectiveMap.AddPair(typeVarInfo, NETTypeVarInfo);
                    }
            }
            // refinement-указание концепта
            else
            {
                // TODO
                this.environment.ErrorManager.AddSemanticError(new SemanticError(
                    "Уточнение концептов не поддерживается в данной версии", node.Location));
            }
            
        }

        public void Visit(ConceptBodyNode node)
        {
            foreach (InnerConceptDeclarationNode innerDecl in node.Declarations)
            {
                if (!this.success)
                    return;
                Visit(innerDecl);
            }
            if (!this.success)
                return;
            UnifyAlgo unification = new UnifyAlgo(environment, currentConceptContext.ConceptData, 
                currentConceptContext.TypeConstraints);
            this.success = unification.Unify();
            this.currentConceptContext.ApplySubstitution(unification.TypesSubstitution);
            if (this.success)
                this.currentConceptContext.UpdateConstraints(unification.ResultConstraints);
            if (this.currentConceptContext.ConceptData.HasEqualTypeParamsOrAssociatedTypes)
                environment.ErrorManager.AddSemanticError(new SemanticError(String.Format(
                    "Концепт {0} содержит параметры или ассоциированные типы, соответствующие одинаковым типам",
                    this.currentConceptContext.ConceptName)));
            var badParams = this.currentConceptContext.ConceptData.BadTypeParams;
            if (badParams.Count > 0)
                environment.ErrorManager.AddSemanticError(new SemanticError(String.Format(
                    "Концепт {0} содержит параметры, не являющиеся типовыми переменными: {1}",
                    this.currentConceptContext.ConceptName, _SetToString(badParams))));
            var constrainedParams = this.currentConceptContext.ConceptData.ConstrainedTypeParams;
            if (constrainedParams.Count > 0)
                environment.ErrorManager.AddWarning(new Warning(String.Format(
                    "Концепт {0} не является полностью адаптируемым, так как содержит параметры с ограничением на подтипирование: {1}",
                    this.currentConceptContext.ConceptName, _SetToString(constrainedParams))));
            this.environment.ErrorManager.AddDebugMessage(
                new DebugInfo(this.currentConceptContext.ConceptData.MakeDescription(environment.TypesBijectiveMap)
                    + "Подстановка: " 
                    + unification.TypesSubstitution.ToString()));
        }

        private string _SetToString<T>(HashSet<T> set)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("{ ");
            foreach (T x in set)
                sb.Append(x.ToString() + "; ");
            sb.Append("}");
            return sb.ToString();
        }

        public void Visit(ConceptReferenceNode node)
        {
            // TODO
            this.environment.ErrorManager.AddSemanticError(new SemanticError(
                "Ссылки на концепты не поддерживаются в данной версии", node.Location));
        }

        public void Visit(InnerConceptDeclarationNode node)
        {
            node.AcceptVisitor(this);
        }

        public void Visit(InnerConceptRequirementNode node)
        {
            node.AcceptVisitor(this);
        }

        public void Visit(InnerConceptAliasNode node)
        {
            node.AcceptVisitor(this);
        }

        public void Visit(NestedConceptRequirementNode node)
        {
            // TODO
            this.environment.ErrorManager.AddSemanticError(new SemanticError(
                "Требования вложенных концептов не поддерживаются в данной версии", node.Location));
        }

        public void Visit(AssociatedTypeDeclarationNode node)
        {
            // сначала проверяем имя ассоциированного типа
            if (currentConceptContext.ContainsDefinedName(node.AssociatedTypeName))
            {
                environment.ErrorManager.AddSemanticError(new SemanticError(
                    String.Format("В концепте {0} уже определён элемент с именем {1}",
                        currentConceptContext.ConceptName, node.AssociatedTypeName), 
                    node.Location));
                this.success = false;
                return;
            }
            // имя новое, добавляем его
            var assocTypeInfo = currentConceptContext.AddConceptAssociatedType(node.AssociatedTypeName);
            // TODO
            Type NETAssoceVarInfo = new NETTypeVariable(assocTypeInfo.Name);
            environment.TypesBijectiveMap.AddPair(assocTypeInfo, NETAssoceVarInfo);
            // обрабатываем ограничение, если оно есть
            if (node.Constraint != null)
            {
                BaseType rightTypeArg = _ProcessTypeArgument(node.Constraint.RightTypeArgument);
                if (!this.success)
                    return;
                // если всё хорошо, должно вернуться != null
                System.Diagnostics.Debug.Assert(rightTypeArg != null,
                    "После обработки ограничения тип правого аргумента не должен быть null");
                _ProcessTypesConstraint(assocTypeInfo, rightTypeArg, node.Constraint.Operator);
            }
        }

        private void _ProcessTypesConstraint(BaseType leftTypeArg, BaseType rightTypeArg, TypeConstraintOperator constrOperator)
        {
            BaseTypeConstraint constraint;
            switch (constrOperator)
            {
                case TypeConstraintOperator.EQUAL:
                    constraint = new SametypeConstraint(leftTypeArg, rightTypeArg);
                    break;
                case TypeConstraintOperator.SUBTYPE:
                    constraint = new SubtypingConstraint(leftTypeArg, rightTypeArg);
                    break;
                default: // supertype
                    constraint = new SubtypingConstraint(rightTypeArg, leftTypeArg);
                    break;
            }
            currentConceptContext.AddTypeConstraint(constraint);
        }

        public void Visit(ConstraintOnTypeArgNode node)
        {
            throw new InvalidOperationException();
        }

        public void Visit(TypeRequirementNode node)
        {
            BaseType leftTypeArg = _ProcessTypeArgument(node.LeftTypeArgument);
            if (!this.success)
                return;
            BaseType rightTypeArg = _ProcessTypeArgument(node.RightTypeArg);
            if (!this.success)
                return;
            System.Diagnostics.Debug.Assert(leftTypeArg != null,
                    "После обработки ограничения тип левого аргумента не должен быть null");
            System.Diagnostics.Debug.Assert(rightTypeArg != null,
                    "После обработки ограничения тип правого аргумента не должен быть null");
            _ProcessTypesConstraint(leftTypeArg, rightTypeArg, node.Operator);
        }

        public void Visit(ConceptAliasNode node)
        {
            // TODO
            this.environment.ErrorManager.AddSemanticError(new SemanticError(
                "Алиасы концептов не поддерживаются в данной версии", node.Location));
        }

        public void Visit(TypeAliasNode node)
        {
            // сначала проверяем имя алиаса
            if (currentConceptContext.ContainsDefinedName(node.Alias))
            {
                environment.ErrorManager.AddSemanticError(new SemanticError(
                    String.Format("В концепте {0} уже определён элемент с именем {1}",
                        currentConceptContext.ConceptName, node.Alias),
                    node.Location));
                this.success = false;
                return;
            }
            BaseType typeArgInfo = _ProcessTypeArgument(node.TypeArgument);
            if (!this.success)
                return;
            System.Diagnostics.Debug.Assert(typeArgInfo != null,
                    "После обработки алиаса тип аргумента не должен быть null");
            currentConceptContext.AddTypeAlias(node.Alias, typeArgInfo);
        }
    }
}
